package jastgen.generator;

import static jastgen.generator.Helper.*;
import static jastgen.util.T3.*;
import static jastgen.util.Util.*;
import static jastgen.generator.Generator.printTypeParams;

import java.io.PrintWriter;
import java.util.*;

import jastgen.CompileException;
import jastgen.nodes.ClassDef;
import jastgen.nodes.Field;
import jastgen.nodes.Module;
import jastgen.nodes.Type;
import jastgen.util.*;

class VisitorImplBodyGenerator implements BodyGenerator {

	private final List<String> visitReturnTypes;
	private final Set<String> nodeTypes;
	private final Set<String> interfaceNames;
	private final Module node;

	VisitorImplBodyGenerator(
			final List<String> visitReturnTypes,
			final Set<String> nodeTypes,
			final Set<String> interfaceNames,
			final Module node
		) throws CompileException {

		this.visitReturnTypes = visitReturnTypes;
		this.nodeTypes = nodeTypes;
		this.interfaceNames = interfaceNames;
		this.node = node;
	}

	public void generate(
			final PrintWriter out)
	{
		generateVisitorBody(out, node);
	}

	private void generateVisitorBody(
			final PrintWriter out,
			final Module node)
	{
		final Set<String> nodesToVisit = new TreeSet<String>();

		final Set<T3<String, String, String>> usedContainers =
			new TreeSet<T3<String, String, String>>(
				new Comparator<T3<String, String, String>>() {
					@Override
					public int compare(
							final T3<String, String, String> lhs,
							final T3<String, String, String> rhs)
					{
						int cmp;
						cmp = lhs.e0.compareTo(rhs.e0);
						if(cmp != 0) return cmp;
						cmp = lhs.e2.compareTo(rhs.e2);
						if(cmp != 0) return cmp;
						cmp = lhs.e1.compareTo(rhs.e1);
						if(cmp != 0) return cmp;
						return 0;
					}
				});

		out.println("/**");
		out.println(" * ");
		out.println(" */");

		out.print("public abstract class VisitorImpl");

		// VisitorImplとVisitorStubでほぼ共通
		// (違いはextendsまたはimplementsの行とスペースの数のみ)
		printTypeParams(out, visitReturnTypes, "                                  ");
		out.println();
		out.print("	implements Visitor");
		printTypeParams(out, visitReturnTypes, "	                   ");
		out.println();
		out.print("{");

		// クラスごとのメソッドを出力
		for(final ClassDef i : node.getClassDefs())
			printClassDef(i, nodesToVisit, usedContainers);

		// ノードクラスを要素とするコンテナの全要素をvisitするメソッドを出力
		for(final T3<String, String, String> c : usedContainers)
			printVisitContainer(out, c.e0, c.e1, c.e2);

		// コンテナ以外のノードクラスをvisitするメソッドを出力
		for(final String t : nodesToVisit)
			printVisit(out, t);

		out.println("}");
	}

	private void printClassDef(
			final ClassDef i,
			final Set<String> nodesToVisit,
			final Set<T3<String, String, String>> usedContainers)
	{
		for(final Field f : i.getFields()) {
			if(!f.hasBody())
				generateVisitorBodyMember(
						f,
						usedContainers,
						nodesToVisit);
		}
	}

	// VoidVisitorImpl、VoidVisitorAdapter、VisitorImpl、NodeReplacer、TreePrinterの5つは大部分が共通
	private void generateVisitorBodyMember(
			final Field node,
			final Set<T3<String, String, String>> usedContainers,
			final Set<String> nodesToVisit)
	{
		final Type type = node.getType();

		if(type.maybeContainerToVisit()) {
			final String outer = type.getName();
			final String inner = type.getOnlyParam().toString();
			if(nodeTypes.contains(inner)) {
				// 型パラメータが1つで、ノードクラスであり、
				// 外側のクラスはcontainerPrefixesに含まれる場合。

				usedContainers.add(tuple(
						outer,
						type.getContainerImplClassName(),
						inner));

				if(interfaceNames.contains(inner))
					nodesToVisit.add(inner);

				return;
			}
		}

		final String t = type.toString();
		if(nodeTypes.contains(t)) {
			if(interfaceNames.contains(t))
				nodesToVisit.add(t);
			return;
		}
	}

	private void printVisit(
			final PrintWriter out,
			final String t)
	{
		out.println();
		out.println("	public final T" + t + " in" + t + "(");
		out.println("			final " + t + " n)");
		printNullableVisit(out);
	}

	private void printVisitContainer(
			final PrintWriter out,
			final String c0,
			final String c1,
			final String e)
	{
		// 常に空文字列でも結果は同じ (パフォーマンスの改善)
		final String arg = eq(c1, "ArrayList") ? "ns.size()" : "";

		out.println();
		out.println("	public final " + c0 + "<T" + e + "> in" + e + "(");
		out.println("			final " + c0 + "<" + e + "> ns)");
		out.println("	{");
		out.println("		if(ns == null)");
		out.println("			return null;");
		out.println("		final " + c0 + "<T" + e + "> xs =");
		out.println("			new " + c1 + "<T" + e + ">(" + arg + ");");
		out.println("		for(final " + e + " n : ns)");
		out.println("			xs.add(in" + e + "(n));");
		out.println("		return xs;");
		out.println("	}");
	}
}
